package org.infodavid.common.model.impl;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.infodavid.common.model.ICategory;
import org.infodavid.common.model.impl.AbstractImprovedDataObjectImpl;

/**
 * The Class AbstractCategory.
 * @param <T> the generic type
 */
public abstract class AbstractCategory<T extends ICategory<T>> extends
    AbstractImprovedDataObjectImpl<Long> implements ICategory<T> {

	/** serialVersionUID. */
	private static final long serialVersionUID = 6523279718688638449L;

	/** The comment. */
	private String comment;

	/** The name. */
	private String name;

	/** The parent category. */
	private T parent;

	/** The sub-categories. */
	private Map<String,T> subCategories;

	/**
	 * constructor.
	 * @param keyArg the key
	 * @throws Exception the exception
	 */
	protected AbstractCategory(final Long keyArg) throws Exception {
		this();

		setKey(keyArg);
	}

	/**
	 * constructor.
	 * @throws Exception the exception
	 */
	protected AbstractCategory() throws Exception {
		super();
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @return the comment
	 * @see org.infodavid.common.persistence.org.infodavid.common.model.ICommented#getComment()
	 */
	public String getComment() {
		return comment;
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @return the name
	 * @see org.infodavid.common.persistence.org.infodavid.common.model.INamed#getName()
	 */
	public String getName() {
		return name;
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @return the parent
	 * @see org.infodavid.common.model.ICategory#getParent()
	 */
	public T getParent() {
		return parent;
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @return the sub categories
	 * @see org.infodavid.common.model.ICategory#getSubCategories()
	 */
	public Map<String,T> getSubCategories() {
		return subCategories;
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @throws Exception the exception
	 * @see org.infodavid.common.impl.model.DataObjectImpl#init()
	 */
	@Override
	public void init() throws Exception {
		super.init();

		comment = null;
		name = null;
		parent = null;
		subCategories = new HashMap<String,T>();
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param value the new comment
	 * @see org.infodavid.common.persistence.org.infodavid.common.model.ICommented#setComment(java.lang.String)
	 */
	public void setComment(final String value) {
		if (!equals(comment, value)) {
			final Object old = comment;
			comment = value;
			markModified(COMMENT, old, comment);
		}
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param value the new parent
	 * @see org.infodavid.common.model.ICategory#setParent(org.infodavid.common.model.ICategory)
	 */
	public void setParent(final T value) {
		if (!(value == this || value != null && getKey() != null && getKey().equals(value.getKey()))) {
			if (!equals(parent, value)) {
				final Object old = parent;
				parent = value;
				markModified(PARENT, old, parent);
			}
		}
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param value the value
	 * @see org.infodavid.common.model.ICategory#addSubCategory(org.infodavid.common.model.ICategory)
	 */
	@SuppressWarnings("unchecked")
	public void addSubCategory(final T value) {
		if (value != null) {
			getSubCategories().put(value.getName(), value);
			value.setParent((T)this);
		}
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param value the value
	 * @see org.infodavid.common.model.ICategory#removeSubCategory(org.infodavid.common.model.ICategory)
	 */
	public void removeSubCategory(final T value) {
		if (value != null) {
			if (getSubCategories().remove(value.getName()) != null) {
				value.setParent(null);
			}
		}
	}

	/**
	 * Sets the sub categories.
	 * @param values the values
	 */
	public void setSubCategories(final Map<String,T> values) {
		final Object old = subCategories;
		subCategories = values;
		markModified(SUBCATEGORIES, old, subCategories);
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param value the new name
	 * @see org.infodavid.common.persistence.org.infodavid.common.model.INamed#setName(java.lang.String)
	 */
	public void setName(final String value) {
		if (!equals(name, value)) {
			final Object old = name;
			name = value;
			markModified(NAME, old, name);
		}
	}

	/**
	 * See super class or interface. (non-Javadoc)
	 * @param builder the builder
	 * @return the string buffer
	 * @see org.infodavid.common.impl.model.DataObjectImpl#toString(java.lang.StringBuffer)
	 */
	@Override
	protected StringBuffer toString(final StringBuffer builder) {
		super.toString(builder);

		builder.append("[comment=");
		builder.append(comment);
		builder.append(", name=");
		builder.append(name);
		builder.append(", subCategories=");

		if (subCategories == null) {
			builder.append("null");
		}
		else {
			builder.append(Arrays.toString(subCategories.keySet().toArray()));
		}

		builder.append("]");

		return builder;
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.model.impl.DataObjectImpl#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		final int result = 1;

		return prime * result + (getName() == null ? 0 : getName().hashCode());
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.model.impl.DataObjectImpl#equals(java.lang.Object)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}

		if (obj == null) {
			return false;
		}

		if (!(obj instanceof ICategory)) {
			return false;
		}

		final ICategory other = (ICategory)obj;

		if (getName() == null) {
			if (other.getName() != null) {
				return false;
			}
		}
		else if (!getName().equals(other.getName())) {
			return false;
		}

		return true;
	}
}
